/*
 *
 */
package com.zhenhe.core.common.utils.pay.bohai.support;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.IOUtils;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.XMLUtils;
import org.apache.xpath.XPathAPI;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.zhenhe.core.common.utils.pay.bohai.BoHaiUtil;


/**
 *  
 */
public class LocalPacProxy {
    public static final String charset = "UTF-8";

    protected static Logger log = BoHaiUtil.log;

    private int port = -1;

    private int timeout = -1;

    private boolean running = false;

    private ServerSocket serverSocket = null;

    private PacKeyManager pacKeyManager = null;

    private boolean multiThreadFlag = true;

    /**
     *  
     */
    public LocalPacProxy() {
        super();
        org.apache.xml.security.Init.init();
    }

    public void start() {
        Socket clientSocket = null;

        if (log.isDebugEnabled()) {
            log.debug(Constants.LOG_PREFIX + "LocalPacProxy start begin!");
        }

        try {
            serverSocket = new ServerSocket(port);
            running = true;

            while (running) {
                try {
                    clientSocket = serverSocket.accept();
                    if (null != clientSocket) {
                        if (timeout != -1) {
                            clientSocket.setSoTimeout(timeout);
                        }

                        if (multiThreadFlag) {
                            new PacProcessThread(clientSocket, pacKeyManager)
                                    .start();
                        } else {
                            singleThread(clientSocket);
                        }
                    }
                } catch (Exception anyE) {
                    anyE.printStackTrace(System.err);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        if (log.isDebugEnabled()) {
            log.debug(Constants.LOG_PREFIX + "LocalpACProxy start end!");
        }
    }

    public void stop() {
        if (log.isDebugEnabled()) {
            log.debug(Constants.LOG_PREFIX + "LocalpACProxy stop begin!");
        }

        running = false;

        if (null != serverSocket) {
            try {
                serverSocket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            serverSocket = null;
        }

        if (log.isDebugEnabled()) {
            log.debug(Constants.LOG_PREFIX + "LocalPacProxy stop end!");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#finalize()
     */
    protected void finalize() throws Throwable {
        stop();

        super.finalize();
    }

    /**
     * @return Returns the port.
     */
    public int getPort() {
        return port;
    }

    /**
     * @param port
     *            The port to set.
     */
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * @return Returns the timeout.
     */
    public int getTimeout() {
        return timeout;
    }

    /**
     * @param timeout
     *            The timeout to set.
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    /**
     * @return Returns the running.
     */
    public boolean isRunning() {
        return running;
    }

    public PacKeyManager getPacKeyManager() {
        return pacKeyManager;
    }

    public void setPacKeyManager(PacKeyManager pacKeyManager) {
        this.pacKeyManager = pacKeyManager;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {

        String transType = "SCSP";//SCSP or BIQ or SCTQ

        Map<String, Object> dataMap = new HashMap<>();
        Map<String, Object> rspMap = null;
        dataMap.put("transType", transType);

        dataMap.put("instId", "100099");
        dataMap.put("certId", "1000992014081101");
        dataMap.put("pyrAct", "2000713325000168");
        dataMap.put("pyrNam", "2000713325");
        dataMap.put("instSeq", "" + System.currentTimeMillis());
        dataMap.put("pyeAct", "330201100120");
        dataMap.put("pyeNam", "测试账户");
        dataMap.put("pyeBnk", "402731057238");
        dataMap.put("amt", "1.01");
        dataMap.put("postscript", "TEST用途");

        Socket socket = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            socket = new Socket("127.0.0.1", 18601);
            socket.setSoTimeout(300000);

            oos = new ObjectOutputStream(socket.getOutputStream());

            oos.writeObject(dataMap);
            oos.flush();
            socket.shutdownOutput();

            ois = new ObjectInputStream(socket.getInputStream());
            rspMap = (Map) ois.readObject();

            oos.close();
            oos = null;

            ois.close();
            ois = null;

            socket.close();
            socket = null;

        } catch (UnknownHostException e) {
            log.error("Error occur !", e);
        } catch (IOException e) {
            log.error("Error occur !", e);
        } catch (ClassNotFoundException e) {
            log.error("Error occur !", e);
        }

        if (null != oos) {
            try {
                oos.close();
            } catch (IOException e) {
                log.error("Error occur !", e);
            }
            oos = null;
        }

        if (null != ois) {
            try {
                ois.close();
            } catch (IOException e) {
                log.error("Error occur !", e);
            }
            ois = null;
        }

        if (null != socket) {
            try {
                socket.close();
            } catch (IOException e) {
                log.error("Error occur !", e);
            }
            socket = null;
        }

        log.info("RspMap:" + rspMap);

        if (null == rspMap) {
            log.error("通讯超时，请稍后再试！");
        }

    }

    private String sign(String xmlContent, String transType) {
        String xmlSign = null;

        try {
            log.info("Begin sign :" + transType + ", xml:" + xmlContent);

            Document doc = string2Doc(xmlContent, charset);

            XMLSignature sign = new XMLSignature(doc, "",
                    XMLSignature.ALGO_ID_SIGNATURE_RSA);

            sign.getSignedInfo().addResourceResolver(new OfflineResolver());

            Node messageNode = doc.getElementsByTagName("Message").item(0);
            messageNode.appendChild(sign.getElement());

            Transforms transforms = new Transforms(doc);
            transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);

            sign.addDocument("#" + transType + "Req", transforms,
                    org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);

            // 签名
            sign.sign(pacKeyManager.getPrivateKey());

            xmlSign = doc2String(doc, charset);
        } catch (Exception e) {
            log.error("Error occur while sign !", e);
        }

        return xmlSign;
    }

    private boolean verify(String xml) {
        boolean flag = false;

        try {
            Document doc = string2Doc(xml, charset);

            Element nscontext = XMLUtils.createDSctx(doc, "ds",
                    org.apache.xml.security.utils.Constants.SignatureSpecNS);

            Element signElement = (Element) XPathAPI.selectSingleNode(doc,
                    "//ds:Signature[1]", nscontext);

            if (null != signElement) {
                XMLSignature signature = new XMLSignature(signElement, "");
                log.info(signature.getBaseLocalName());
                log.info(signature.getBaseNamespace());
                log.info(signature.getBaseURI());

                flag = signature.checkSignatureValue(pacKeyManager
                        .getCertificate().getPublicKey());
            }else{
                log.info("null signElement");
            }
        } catch (Exception e) {
            log.error("Error occur while verify !", e);
        }

        return flag;
    }

    /**
     * @param xml
     * @return
     * @throws Exception
     */
    private Document string2Doc(String xml, String charset) throws Exception {

        InputStream is = IOUtils.toInputStream(xml, charset);

        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
                .newInstance();
        docBuilderFactory.setNamespaceAware(true);
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

        Document doc = null;
        if (null != charset) {
            doc = docBuilder.parse(is, charset);
        } else {
            doc = docBuilder.parse(is);
        }
        return doc;
    }

    /**
     * @param doc
     * @return
     * @throws Exception
     */
    private String doc2String(Document doc, String charset) throws Exception {

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = tf.newTransformer();
        if (null != charset) {
            t.setOutputProperty("encoding", charset);
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        t.transform(new DOMSource(doc), new StreamResult(bos));
        String xmlStr = null;

        if (null != charset) {
            xmlStr = bos.toString(charset);
        } else {
            xmlStr = bos.toString();
        }
        return xmlStr;
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
	private void singleThread(Socket socket){
        if (log.isDebugEnabled()) {
            log.debug("singleThread run begin!");
        }

        try {
            if (null != socket) {

                ObjectInputStream ois = new ObjectInputStream(
                        socket.getInputStream());

                Map<String, Object> dataMap = (Map) ois.readObject();

                String transType = (String) dataMap.get("transType");

                String request = sign(PacUtil.formatData(dataMap), transType);

                String response = PacUtil.issuePac(request);

                log.info("Trans response:" + response);
                dataMap.put("response", response);

                boolean verifyFlag = false;

                if (null != response) {
                    verifyFlag = verify(response);
                    log.info("Verify result:"+verifyFlag);
                }
                
                verifyFlag = true;

                String rspCode = null;
                String rspMsg = "";

                if (verifyFlag) {

                    String errorCode = null;
                    String errorMessage = "";

                    int pos1 = -1;
                    int pos2 = -1;
                    pos1 = response.indexOf("<rspCode>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</rspCode>");
                        rspCode = response.substring(pos1 + 9, pos2);
                    }

                    pos1 = response.indexOf("<rspMsg>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</rspMsg>");
                        rspMsg = response.substring(pos1 + 8, pos2);
                    }

                    if (null == rspCode) {
                        rspCode = "9";
                    }

                    dataMap.put("rspCode", rspCode);
                    dataMap.put("rspMsg", rspMsg);

                    pos1 = response.indexOf("<errorCode>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</errorCode>");
                        errorCode = response.substring(pos1 + 11, pos2);
                    }

                    pos1 = response.indexOf("<errorMessage>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</errorMessage>");
                        errorMessage = response.substring(pos1 + 14, pos2);
                    }

                    if (null != errorCode) {
                        dataMap.put("errorCode", errorCode);
                        dataMap.put("errorMessage", errorMessage);
                    }
                } else {
                    dataMap.put("errorCode", "9999");
                    dataMap.put("errorMessage", "验证服务端签名失败");
                }

                ObjectOutputStream oos = new ObjectOutputStream(
                        socket.getOutputStream());
                oos.writeObject(dataMap);
                oos.flush();

            }
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("send data error ", e);
            }
        }

        if (null != socket) {
            try {
                socket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            socket = null;
        }

        if (log.isDebugEnabled()) {
            log.debug("singleThread run end!");
        }
    }
}